
#region Imported Namespaces
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Data;
using System.Web;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.WebControls;

using DataAccess = PHSRAG.Utility.DataAccess;
#endregion

namespace PHSRAG.Insight21.InsightWebControls
{
	#region Class AccessPrivilegeEditor
	/// <summary>
	/// The AccessPrivilegeEditor control provides a data agnostic way of displaying access bits associated with a set of
	/// </summary>
	/// <remarks>
	/// <protocolId>
	/// The display takes the form of a matrix which contains three types of rows: a module row
	/// which displays the module names, an access right row which displays the name of each access bit, one or more item rows
	/// which dispplay one checkbox per access bit, and a footer row.
	/// The contents of the first column varies as follows:
	/// <list type="bullet">
	/// <item><description>In the module row, it is emtpy</description></item>
	/// <item><description>In the access right row it contains the caption for the first column of all item rows</description></item>
	/// <item><description>In the item row it contains the name of the item</description></item>
	/// </list>
	/// The footer row contains an "Add" button.
	/// </protocolId>
	/// <protocolId>Interacting with the AccessPrivilegeEditor control requires handling of the following events:</protocolId>
	/// <list type="table">
	/// <listheader>
	/// <term>Event</term>
	/// <description>Usage</description>
	/// </listheader>
	/// <item>
	/// <term>ProvideItemList</term>
	/// <description><b>Mandatory.</b> Raised when the data for display is needed. Event handler is expected to return a DataTable.
	/// </description>
	/// </item>
	/// <item>
	/// <term>Add</term>
	/// <description>Raised when the <b>Add</b> button is clicked. Event handler will be provided with a State object
	/// (via the event argument). Refer to the documentation on the State object for its structure.
	/// </description>
	/// </item>
	/// </list>
	/// </remarks>
	/// <example>
	/// <protocolId>Here is an example of how the control is used on a page:</protocolId>
	/// <code>
	/// <![CDATA[
	/// ...
	/// <%@ Register TagPrefix="cc1" Namespace="PHSRAG.Insight21.InsightWebControls" Assembly="PHSRAG.Insight21.InsightWebControls" %>
	/// ...
	/// <cc1:AccessPrivilegeEditor id="accessPrivilegeEditor" runat="server" />
	/// ...
	/// ]]>
	/// </code>
	/// </example>
	[ToolboxData("<{0}:AccessPrivilegeEditor runat=server />"), Designer(typeof(AccessPrivilegeEditorDesigner))]
	public class AccessPrivilegeEditor : System.Web.UI.WebControls.WebControl, INamingContainer
	{
		#region Private Types
		/// <summary>
		/// The ColumnIndex enumeration defines only the first two columns. The total number of columns is data driven.
		/// </summary>
		private enum ColumnIndex
		{
			ItemCaption, Item
		}

		/// <summary>
		/// The RowIndex enumeration defines only the first three rows. The total number of rows is data driven.
		/// </summary>
		private enum RowIndex
		{
			Module, AccessRight, Item
		}

		/// <summary>
		/// The SourceColumnINdex enumeration defines the sequence of columns in the source DataTable.
		/// </summary>
		private enum SourceColumnIndex
		{
			Module, AccessRight, AccessRightBitValue, IsSelected, ID, Name, IsContact, Comment
		}
		#endregion

		#region Constants
		private const string AddCommand = "Add";
		private const string EnableEditKey = "EnableEdit_17B189A7-336C-4e83-8B71-2D1FA2B6D81F";
		private const string ItemList = "ItemList";
		private const string ValueAttribute = "Value";

		private const string AccessRightColumn = "AccessRight";
		private const string AccessRightBitValueColumn = "AccessRightBitValue";
		private const string CommentColumn = "Comment";
		private const string ModuleColumn = "Module";
		private const string IsContactColumn = "Is Contact";
		private const string IsSelectedColumn = "IsSelected";
		private const string ItemIDColumn = "Id";
		private const string ItemNameColumn = "Name";
		private const string ItemSelectedColumn = "IsSelected";
		#endregion

		#region Instance Variables
		private Button	add;
		private string addButtonText = "Add";
		private bool	isExtendedRights;
		private Table	table;
		#endregion

		#region Delegates
		/// <summary>Delegate for the ProvideItemList event.</summary>
		public delegate DataTable ItemListHandler(AccessPrivilegeEditor source, out string itemCaption);
		#endregion

		#region Events
		/// <summary>
		/// Add/Remove handler for the event associated with clicking the Add button.
		/// </summary>
		[Category("Action"), Description("Raised when the Add buttion is clicked")]
		public event CommandEventHandler Add
		{
			add		{ Events.AddHandler(AddCommand, value); }
			remove	{ Events.RemoveHandler(AddCommand, value); }
		}

		/// <summary>
		/// Add/Remove handler for the event associated with getting the item list.
		/// </summary>
		[Category("Action"), Description("Raised when the item list is required")]
		public event ItemListHandler ProvideItemList
		{
			add		{ Events.AddHandler(ItemList, value); }
			remove	{ Events.RemoveHandler(ItemList, value); }
		}
		#endregion

		#region Public Properties
		/// <summary>
		/// Get/Set the Add Button Text. 
		/// </summary>
		[Bindable(true), Category("Behavior"), DefaultValue("")] 
		public string AddButtonText
		{
			get { return addButtonText;	}
			set	{ addButtonText = value; }
		}
		/// <summary>
		/// Get the current consolidated state.
		/// </summary>
		public State CurrentState { get { return BuildState(); } }

		/// <summary>
		/// Get/Set the predicate to enable changing the state of checkboxes.
		/// </summary>
		public bool EnableEdit
		{
			get { object o = ViewState[EnableEditKey]; return (o == null) ? false : (bool)o; }
			set { ViewState[EnableEditKey] = value; }
		}
		#endregion

        #region Public Methods
        /// <summary>
        /// Allows the control to be refreshed as needed.
        /// </summary>
        public void Refresh()
        {
            CreateChildControls();
        }
        #endregion

		#region Overrides
		/// <summary>
		/// Ensure that all child controls are created and return their collection.
		/// </summary>
		public override ControlCollection Controls
		{
			get { EnsureChildControls(); return base.Controls; }
		}

		/// <summary>
		/// Create all controls that will be on the AccessPrivilegeEditor. Notice that the creation is more than just
		/// instantiating the controls. It also involves in generating the underlying HTML so that the controls shows up
		/// per the need.
		/// </summary>
		protected override void CreateChildControls()
		{
			if (!IsDesignMode)
			{
				Controls.Clear();

				table = new Table();
				table.CssClass = "AccessPrivilegeEditor";
				Controls.Add(table);

				CreateControls();
				GenerateControlTree();
			}
		}

		/// <summary>
		/// Invoked when an event is raised by a child control. If an event handler has been associated with the Add
		/// event, invoke it with the consolidated state of the control.
		/// </summary>
		/// <param name="source">Reference to the child control that raised the event</param>
		/// <param name="e">Argument associated with the event</param>
		/// <returns>True if the event was handle by this method; false otherwise</returns>
		protected override bool OnBubbleEvent(object source, EventArgs e)
		{
			bool handled = false;
			if ((e is CommandEventArgs) && (source is Button))
			{
				CommandEventHandler handler = (CommandEventHandler)Events[AddCommand];
				if (handler != null)
				{
					handler(this, new CommandEventArgs(AddCommand, CurrentState));
					handled = true;
				}
			}
			return handled;
		}

		/// <summary>
		/// Enable/disable all check-boxes, text-boxes and the Add button.
		/// </summary>
		/// <param name="e">Argument associated with the event.</param>
		protected override void OnPreRender(EventArgs e)
		{
			base.OnPreRender(e);

			bool enableEdit = EnableEdit;
			add.Enabled = enableEdit;

			int columnCount = table.Rows[(int)ColumnIndex.Item].Cells.Count;
			int rowCount = table.Rows.Count - (int)RowIndex.Item;
			for (int r = (int)RowIndex.Item; r <= rowCount; ++r)
			{
				TableRow itemRow = table.Rows[r];
                for (int c = (int)ColumnIndex.Item; c < columnCount; ++c)
                {
                    ((WebControl)itemRow.Cells[c].Controls[0]).Enabled = enableEdit;
                }
			}
		}
		#endregion

		#region Private Properties
		/// <summary>
		/// Get the design mode.
		/// </summary>
		private static bool IsDesignMode
		{
			get { return HttpContext.Current == null; }
		}
		#endregion

		#region Private Methods
		/// <summary>
		/// Build and return a State object which is a consolidation of the state of each item in the control.
		/// The state is built by iterating over each item row and over each header column within each row. For each row,
		/// the bitwise OR of the value from the corresponding header cell for each checked checkbox is computed and used
		/// as the value of the item associated with that row.
		/// The row iteration ignores the module, header, and footer rows.
		/// </summary>
		/// <returns>Reference to a State object</returns>
		private State BuildState()
		{
			return isExtendedRights ? BuildExtendedState() : BuildNormalState();
		}

		/// <summary>
		/// Build and return a State object which is a consolidation of the state of each item in the control.
		/// The state is built by iterating over each item row and over each header column within each row. For each row,
		/// the bitwise OR of the value from the corresponding header cell for each checked checkbox is computed and used
		/// as the value of the item associated with that row.
		/// The row iteration ignores the module, header, and footer rows.
		/// </summary>
		/// <returns>Reference to a State object</returns>
        private State BuildExtendedState()
        {
            Hashtable items = new Hashtable();
            for (int row = (int)RowIndex.Item, rowCount = table.Rows.Count - 1; row < rowCount; ++row)
            {
                TableRow itemRow = table.Rows[row];
                TableRow accessRightRow = table.Rows[(int)RowIndex.AccessRight];
                int itemValue = 0;
                int columnCount = accessRightRow.Cells.Count - 2; // Last two columns (IsContact, Comments) handled separately.
				
                for (int column = (int)ColumnIndex.Item; column < columnCount; ++column)
                {
                    if (((CheckBox)itemRow.Cells[column].Controls[0]).Checked)
                        itemValue |= Convert.ToInt32(accessRightRow.Cells[column].Attributes[ValueAttribute]);
                }

				
                TableCell itemCell = itemRow.Cells[(int)ColumnIndex.ItemCaption];

                Triplet triplet;
                if (items.ContainsKey(itemCell.Attributes[ValueAttribute]))
                {
                    // the hashtable already contains an entry for the resourceId update the rights bitmask
                    triplet = (Triplet)((Pair)items[itemCell.Attributes[ValueAttribute]]).Second;
                    triplet.First = (int)triplet.First | itemValue;
                }
                else
                {
                    // the hashtable does not already contain an entry for the resource id so create a new rights bitmask
                    triplet = new Triplet(itemValue, ((CheckBox)itemRow.Cells[columnCount].Controls[0]).Checked,
                        ((TextBox)itemRow.Cells[columnCount + 1].Controls[0]).Text);
                }

                items[itemCell.Attributes[ValueAttribute]] = new Pair(itemCell.Attributes[ValueAttribute], triplet);
            }

            return new State(table.Rows[(int)RowIndex.AccessRight].Cells[(int)ColumnIndex.ItemCaption].Text, items);
        }

		/// <summary>
		/// Build and return a State object which is a consolidation of the state of each item in the control.
		/// The state is built by iterating over each item row and over each header column within each row. For each row,
		/// the bitwise OR of the value from the corresponding header cell for each checked checkbox is computed and used
		/// as the value of the item associated with that row.
		/// The row iteration ignores the module, header, and footer rows.
		/// </summary>
		/// <returns>Reference to a State object</returns>
		private State BuildNormalState()
		{
			Hashtable items = new Hashtable();
			for (int row = (int)RowIndex.Item, rowCount = table.Rows.Count - 1; row < rowCount; ++row)
			{
				TableRow itemRow = table.Rows[row];
				TableRow accessRightRow = table.Rows[(int)RowIndex.AccessRight];
				int itemValue = 0;

				for (int column = (int)ColumnIndex.Item, columnCount = accessRightRow.Cells.Count;
					column < columnCount; ++column)
				{
					if (((CheckBox)itemRow.Cells[column].Controls[0]).Checked)
						itemValue |= Convert.ToInt32(accessRightRow.Cells[column].Attributes[ValueAttribute]);
				}

				TableCell itemCell = itemRow.Cells[(int)ColumnIndex.ItemCaption];
				items[itemCell.Text] = new Pair(itemCell.Attributes[ValueAttribute], itemValue);
			}

			return new State(table.Rows[(int)RowIndex.AccessRight].Cells[(int)ColumnIndex.ItemCaption].Text, items);
		}

		/// <summary>
		/// Create the header columns to display the name of each checkbox. The span of the column in the module row
		/// for the associated module is set to the number of items which will be displayed for that module.
		/// Each header cell is associated with the value specified for that header. This allows users to associate any
		/// arbitray value with each header. When the state is built (in response to a click on the Add) button, the
		/// bitwise OR is done on the value of the header for each checked checkbox.
		/// The header row is the second row in the table.
		/// </summary>
		/// <param name="module">Module associated with the header list</param>
		/// <param name="accessRightRows">Array of DataRow containing access right names</param>
		/// <param name="accessRightRow">Reference to the row within which the columns will be created</param>
		private void CreateAccessRightColumns(string module, DataRow[] accessRightRows, TableRow accessRightRow)
		{
			DataTable dt = accessRightRows[0].Table.Clone();
			dt.TableName = module;
			foreach (DataRow row in accessRightRows)
				dt.ImportRow(row);
			DataTable accessRightTable = DataAccess.SelectDistinct(dt, AccessRightColumn, true);

			foreach (TableCell cell in table.Rows[(int)RowIndex.Module].Cells)
			{
				if (cell.Text == module)
				{
					cell.ColumnSpan = accessRightTable.Rows.Count;
					break;
				}
			}

			foreach (DataRow accessRight in accessRightTable.Rows)
			{
				TableCell cell = new TableCell();
				cell.CssClass = "HeaderCell";
				cell.Text = (string)accessRight[AccessRightColumn];
				cell.Attributes[ValueAttribute] = accessRight[AccessRightBitValueColumn].ToString();
				accessRightRow.Cells.Add(cell);
			}
		}

		/// <summary>
		/// Create the access-rights row to display header names for each module. This is the second row in the table.
		/// The first column of the header row displays the item caption.
		/// When an extended-rights data table is the source, we have to create two additional items in the access-rights row;
		/// IsContact, and Comment.
		/// </summary>
		/// <param name="dataTable">DataTable which contains the entire data</param>
		/// <param name="moduleTable">DataTable containing list of modules</param>
		/// <param name="itemCaption">Caption for use in the AccessRight row for the</param>
		private void CreateAccessRightRow(DataTable dataTable, DataTable moduleTable, string itemCaption)
		{
			TableRow accessRightRow = new TableRow();
			accessRightRow.CssClass = "Header";
			table.Rows.Add(accessRightRow);
			
			TableCell captionCell = new TableCell();
			captionCell.CssClass = "HeaderCaptionCell";
			captionCell.Text = itemCaption;
			accessRightRow.Cells.Add(captionCell);

			foreach (DataRow moduleRow in moduleTable.Rows)
			{
				string module = (string)moduleRow[ModuleColumn];
				DataRow[] accessRightRows = dataTable.Select(string.Format("{0}='{1}'", ModuleColumn, module));
				CreateAccessRightColumns(module, accessRightRows, accessRightRow);
			}

			if (isExtendedRights)
			{
				TableCell cell = new TableCell();
				cell.CssClass = "HeaderCell";
				cell.Text = IsContactColumn;
				accessRightRow.Cells.Add(cell);

				cell = new TableCell();
				cell.CssClass = "HeaderCell";
				cell.Text = CommentColumn;
				accessRightRow.Cells.Add(cell);
			}
		}

		/// <summary>
		/// Create the footer row which will contain the Add button which will be positioned in the last column.
		/// </summary>
		private void CreateFooterRow()
		{
			TableRow footerRow = new TableRow();
			table.Rows.Add(footerRow);

			TableCell fillerCell = new TableCell();
			footerRow.Cells.Add(fillerCell);
			fillerCell.ColumnSpan = table.Rows[(int)RowIndex.AccessRight].Cells.Count - 1;

			TableCell buttonCell = new TableCell();
			footerRow.Cells.Add(buttonCell);
			buttonCell.CssClass = "ButtonCell";

			add = new Button();
			add.CommandName = AddCommand;
			add.Text = addButtonText;
			add.CssClass = "Button";
			buttonCell.Controls.Add(add);
		}

		/// <summary>
		/// Create an item row to display the checkboxes associated with an item. The first column of the row displays
		/// the item caption and all other columns display one checkbox each. The checked state of the checkboxes will
		/// be set if an event handler has been registred for the ProvideItemState event, and if that event handler returns
		/// a non-null collection of bools. The default is to leave all checkboxes unchecked.
		/// When an extended-rights data table is the source, we have to create two additional items in each item row;
		/// a checkbox (IsContact), and a text box (Comment).
		/// </summary>
		private void CreateItemRows(DataTable dataTable, DataTable moduleTable)
		{
			bool isExtendedItems = ((dataTable.Columns.Count - 1) == (int)SourceColumnIndex.Comment);	// count is 1-based.

            DataTable itemName = DataAccess.SelectDistinct(dataTable, isExtendedRights ? ItemNameColumn : ItemIDColumn, true);
            //we should always sort by name
			DataView dataView = new DataView(itemName);
			dataView.Sort = ItemNameColumn;

			foreach(DataRowView itemRow in dataView )
			{
				TableRow row = new TableRow();
				TableCell itemCell = new TableCell();
				itemCell.Text = string.Format(@"<nobr>{0}</nobr>", itemRow[ItemNameColumn]);
				itemCell.CssClass = "ItemCell";
				row.Cells.Add(itemCell);
				table.Rows.Add(row);

				DataRow[] rows = dataTable.Select(string.Format(@"{0}='{1}'", ItemIDColumn, itemRow[ItemIDColumn]));
				itemCell.Attributes[ValueAttribute] = rows[0][ItemIDColumn].ToString();

				TableRow accessRightRow = table.Rows[(int)RowIndex.AccessRight];

				int count = accessRightRow.Cells.Count;
				if (isExtendedRights)
					count -= 2;	// Extended-rights data table contains two additional columns (IsContact, Comment).

				for (int i = (int)ColumnIndex.Item; i < count; ++i)
				{
					itemCell = new TableCell();
					itemCell.CssClass = "ItemCheckBoxCell";
								
					CheckBox itemCheckBox = new CheckBox();
					itemCell.Controls.Add(itemCheckBox);
					row.Cells.Add(itemCell);

					rows = dataTable.Select(string.Format(@"{0}='{1}' AND {2}='{3}'",
						ItemIDColumn, itemRow[ItemIDColumn], AccessRightBitValueColumn, accessRightRow.Cells[i].Attributes[ValueAttribute]));
					itemCheckBox.Checked = Convert.ToBoolean(rows[0][ItemSelectedColumn]);
				}

				if (isExtendedItems)
				{
					itemCell = new TableCell();
					itemCell.CssClass = "ItemCheckBoxCell";
								
					CheckBox itemCheckBox = new CheckBox();
					itemCell.Controls.Add(itemCheckBox);
					row.Cells.Add(itemCell);

					rows = dataTable.Select(string.Format(@"{0}='{1}'", ItemIDColumn, itemRow[ItemIDColumn]));
					itemCheckBox.Checked = Convert.ToBoolean(rows[0][IsContactColumn]);

					itemCell = new TableCell();
					itemCell.CssClass = "ItemTextBoxCell";
								
					TextBox itemTextBox = new TextBox();
					itemCell.Controls.Add(itemTextBox);
					row.Cells.Add(itemCell);

					rows = dataTable.Select(string.Format(@"{0}='{1}'", ItemIDColumn, itemRow[ItemIDColumn]));
					itemTextBox.Text = rows[0][CommentColumn].ToString();
				}
			}
		}

		/// <summary>
		/// Create the module row which groups one or more headers together. This is the first row in the table.
		/// </summary>
		/// <param name="dataTable">DataTable containing entire information</param>
		private DataTable CreateModuleRow(DataTable dataTable)
		{
			TableRow row = new TableRow();
			row.CssClass = "ModuleHeader";

			TableHeaderCell headerCell = new TableHeaderCell();
			row.Cells.Add(headerCell);

			DataTable moduleTable = DataAccess.SelectDistinct(dataTable, ModuleColumn, false);

			foreach (DataRow dataRow in moduleTable.Rows)
			{
				headerCell = new TableHeaderCell();
				headerCell.Text = (string)dataRow[ModuleColumn];
				row.Cells.Add(headerCell);
			}

			table.Rows.Add(row);
			return moduleTable;
		}

		/// <summary>
		/// Create all controls for run-time. Ensure that all mandatory events have handlers associated with them.
		/// Creation essentially does the following:
		/// 1. Create the table.
		/// 2. Create the module row.
		/// 3. Create the header row.
		/// 4. Create item rows.
		/// 5. Create the footer row.
		/// </summary>
		private void CreateControls()
		{
			ItemListHandler handler = Events[ItemList] as ItemListHandler;
			if (handler == null)
				throw new Exception(string.Format("AccessPrivilegeEditor requires handling of the {0} event", ItemList));

			string itemCaption;
			DataTable dataTable = handler(this, out itemCaption);
			if (dataTable == null)
				throw new Exception("AccessPrivilegeEditor requires a non-null Datatable reference");

			isExtendedRights = ((dataTable.Columns.Count - 1) == (int)SourceColumnIndex.Comment);	// count is 1-based.

			DataTable moduleTable = CreateModuleRow(dataTable);
			CreateAccessRightRow(dataTable, moduleTable, itemCaption);
			CreateItemRows(dataTable, moduleTable);
			CreateFooterRow();
		}

		/// <summary>
		/// Setup the control tree to reflect the actual (and exact) layout of the controls within the Pager.
		/// </summary>
		private void GenerateControlTree()
		{
			Controls.Add(table);
		}
		#endregion

		#region Class State
		/// <summary>
		/// The State class is the consolidation of the state of all items in the AccessPrivilegeEditor. Its structure is
		/// as follows:
		/// <para>
		/// Pair(First: item caption, Second: Hashtable(Key: item name, Value: bitwise-OR of
		/// all checkbox state for that item))
		/// </para>
		/// This class is used simply as an encapsulation mechanism and a convenient way to pass state to the control's
		/// container.
		/// </summary>
		public class State
		{
			#region Instance Variables
			private string		itemCaption;
			private Hashtable	items;
			#endregion

			#region Public Properties
			/// <summary>Get the item caption</summary>
			public string ItemCaption { get { return itemCaption; } }

			/// <summary>Get the Hashtable which contains the consolidated state of all items</summary>
			public Hashtable Items { get { return items; } }
			#endregion

			#region Constructor
			/// <summary>
			/// Constructor for the State class. It is given internal linkage since only the AccessPrivilegeEditor creates it.
			/// </summary>
			/// <param name="itemCaption">caption of the item</param>
			/// <param name="items">Consolidated state</param>
			internal State(string itemCaption, Hashtable items)
			{
				this.itemCaption = itemCaption;
				this.items = items;
			}
			#endregion
		}
		#endregion
	}
	#endregion

	#region Class PagerDesigner
	/// <summary>
	/// Designer associated with the AccessPrivilegeEditorDesigner control.
	/// </summary>
	/// <remarks>
	/// This class cannot be used directly.
	/// </remarks>
	public class AccessPrivilegeEditorDesigner : ControlDesigner
	{
		#region Overrides
		/// <summary>
		/// Provide an empty representation of the control (shows type/instance name).
		/// </summary>
		/// <returns>HTML representation of the Pager control</returns>
		public override string GetDesignTimeHtml()
		{
			return base.GetEmptyDesignTimeHtml();
		}
		#endregion
	}
	#endregion
}

// --- EOF ---
